/**
 * @file simple_message_server_1186.c
 *
 * TCP/IP Simple Message Board - Server
 *
 * This ist the simple_message_server_1186, which is inspired by the reference
 * implementation simple_message_server. It receives a message from
 * the simple_message_client_1186 and redirects it to simple_messer_server_logic.
 **
 * @author Robert Grasböck <rober.grasboeck@technikum-wien.at>
 * @author David Schreiber <david.schreiber@technikum-wien.at>

 * @date 2010/12/07
 * @version $Revision: 1 $
 *
 * @todo More or less everything
 */

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>

/* VERBOSE pre processor macro */
#define VERBOSE(format, ...)    \
do { if(cfg.verbose == 1) { \
    printf("%s [%s, %s(), line: %d]: ", cfg.applicationName, __FILE__, __FUNCTION__, __LINE__); \
    printf(format, ## __VA_ARGS__); } \
} while (0)

/* ERROR pre processor macro */
#define ERROR(format, ...)    \
do { \
    fprintf(stderr, "%s: ", cfg.applicationName); \
    fprintf(stderr, format, ## __VA_ARGS__);  \
    exit(1); \
} while (0)

/** Number of simultanous client connections */
#define BACKLOG 3
/** Buffer size used for sending/receiving */
#define BUFFER_SIZE 256

#define STDIN 0
#define STDOUT 1

/**
 * This struct is for easily passing the application options given by command line agruments
 */
struct applicationOptions {
    char*   applicationName;    /* Application name (execution name) */
    char*   port;               /* Server port */
    int     help;               /* Print help/usage message */
};

/**
 * GLOBALS
 */
/* Global configuration */
static struct applicationOptions cfg = { NULL, NULL, 0 };

/**
 * This function prints the usage message / help
 */
void print_usage(void) {
    printf("usage: %s option\n", cfg.applicationName);
    printf("options:\n");
    printf("\t-p, --port <port>\n");
    printf("\t-h, --help\n");
}

/**
 * This function parses the command line arguments given with execution
 * and saves them inside the application_options structure
 *
 * @param [in] argc		argument count
 * @param [in] argv		arguments
 */
void parse_arguments(int argc, char* argv[]) {
    int c = 0;              /* The current option */
    int optionIndex = 0;    /* The index of currently parsed option */
    int noparam = 1;        /* Used to check if no option was passed at all (short and long) */
    static struct option longopts[] = {
        { "port",   required_argument,  NULL, 'p' },
        { "help",   no_argument,        NULL, 'h' }
    };

    /* Parse all command line options */
    while((c = getopt_long(argc, argv, "p:h", longopts, &optionIndex)) != -1) {
        /* Set this to zero after first option found */
        noparam = 0;
        /* Do configuration regarding to option */
        switch(c) {
            case 'p': cfg.port = optarg; break;
            case 'h': cfg.help = 1; break;
            default:  cfg.help = 1; break;
        }
    }

    /* If no option was passed (while-loop never entered),
       or more arguments than options where given, display the usage message */
    if(noparam == 1 || optind < argc) cfg.help = 1;
}

/**
 * This function creates an IPv4 socket based on the server info
 *
 * @param server_info   - The pre fetched server info of getaddrinfo() call
 * @return int          - Created IPv4 socket
 */
int create_ipv4_socket(struct addrinfo *server_info) {
    int fd_socket = 0;                  /* IPv4 socket handle */
    struct sockaddr_in* socket_addr_ip4;/* IPv4 socket address */
    char ip4_addr[INET_ADDRSTRLEN];     /* Representing the socket IP address */
    unsigned short int ip4_port;        /* Representing the socket port */

    /* Cast the generic socket address into IPv4 format*/
    socket_addr_ip4 = (struct sockaddr_in*) server_info->ai_addr;

    /* Create the human readable IP string */
    inet_ntop(PF_INET, &(socket_addr_ip4)->sin_addr, ip4_addr, INET_ADDRSTRLEN);

    /* Create the human readable Port */
    ip4_port = htons(socket_addr_ip4->sin_port);

    /* Create the new socket */
    fd_socket = socket(PF_INET, server_info->ai_socktype, server_info->ai_protocol);

    /* Did it work? */
    if(fd_socket == 0) ERROR("Cannot open socket\n");

    return fd_socket;
}

/**
 * This function creates an IPv6 socket based on the server info
 *
 * @param server_info - The pre fetched server info of getaddrinfo() call
 * @return int - Created IPv6 socket
 */
int create_ipv6_socket(struct addrinfo *server_info) {
    int fd_socket = 0;                  /* IPv6 socket handle */
    struct sockaddr_in6* socket_addr_ip6;/* IPv6 socket address */
    char ip6_addr[INET6_ADDRSTRLEN];     /* Representing the socket IP address */
    unsigned short int ip6_port;        /* Representing the socket port */

    /* Cast the generic socket address into IPv4 format*/
    socket_addr_ip6 = (struct sockaddr_in6*) server_info->ai_addr;

    /* Create the human readable IP string */
    inet_ntop(PF_INET6, &(socket_addr_ip6)->sin6_addr, ip6_addr, INET6_ADDRSTRLEN);

    /* Create the human readable Port */
    ip6_port = htons(socket_addr_ip6->sin6_port);

    /* Create the new socket */
    fd_socket = socket(PF_INET6, server_info->ai_socktype, server_info->ai_protocol);

    /* Did it work? */
    if(fd_socket == 0) ERROR("Cannot open socket\n");

    return fd_socket;
}

/**
 * This function creates an IPv4 or IPv6 TCP socket based on the system config
 *
 * @param [out] fd_socket   - The created TCP socket
 */
void create_listener_socket(int *fd_socket) {
    struct addrinfo server_hints;       /* Socket hinting */
    struct addrinfo *local_addr = NULL; /* Local socket information*/

    /* Set well defined values (0) */
    memset(&server_hints, 0, sizeof server_hints);

    /* Don't care for IPv4 or IPv6 */
    server_hints.ai_family = AF_UNSPEC;

    /* Only use TCP stream socket */
    server_hints.ai_socktype = SOCK_STREAM;

    /* Use local IP for server */
    server_hints.ai_flags = AI_PASSIVE;

    /* Resolve my own address based on hints */
    if(getaddrinfo(NULL, cfg.port, &server_hints, &local_addr) != 0)
        ERROR("Could not resolve name for given server\n");

    /* Select protocol family */
    switch(local_addr->ai_family) {
        case PF_INET:
            /* Setup IPv4 socket */
            *fd_socket = create_ipv4_socket(local_addr);
            break;
        case PF_INET6:
            /* Setup IPv6 socket */
            *fd_socket = create_ipv6_socket(local_addr);
            break;
        default:
            ERROR("Unsupported protocol family encountered\n");
    }

    /* Bind server to the socket */
    if(bind(*fd_socket, local_addr->ai_addr, local_addr->ai_addrlen) == -1)
        ERROR("Could not bind server to socket.\n");
}

/**
 * This function forks the communication server and launches the server logic
 * application in a child process, which will then communicate with the client.
 *
 * @param [in] fd_client    - Socket with client connection
 */
void link_server_logic(int fd_client) {
    int pid;            /* The process id returned while forking */
    int write_pipe[2];  /* Parent to child write pipe */
    int read_pipe[2];   /* Parent from child read pipe */
    char buffer[BUFFER_SIZE];   /* Read/write buffer */
    int status = 0;     /* Holds various status codes (like bytes written) */

    /* Create a pipe to write to server logic STDIN */
    if(pipe(write_pipe) < 0)
        ERROR("Could not create pipe for writing.\n");

    /* Create a pipe for reading server logic STDOUT */
    if(pipe(read_pipe) < 0)
        ERROR("Could not create pipe for reading.\n");
    
    switch(pid = fork()) {
        case -1: ERROR("Could not fork child process.\n");
        case  0: /* Child process code executing server logic */

            /* Close the client socket handle */
            close(fd_client);
            
            /* Close parent's end of write pipe (not used in child process) */
            close(write_pipe[1]);

            /* Map the parent's write pipe to own STDIN for receiving */
            dup2(write_pipe[0], STDIN);

            /* Close the not longer needed pipe */
            close(write_pipe[0]);

            /* Close parent's end of read pipe (not used in child process) */
            close(read_pipe[0]);

            /* Map the parent's read pipe to own STDOUT for sending */
            dup2(read_pipe[1], STDOUT);

            /* Close the not longer needed pipe */
            close(read_pipe[1]);

            /* Execute server logic which can now communicate with parent */
            if(execlp("simple_message_server_logic", "simple_message_server_logic",NULL) == -1)
                ERROR("Could not run server logic\n");

            break;

        default: /* Parent process handling network communication */

            /* Close child's end of read pipe (not needed in parent process ) */
            close(read_pipe[1]);

            /* Close child's end of write pipe (not needed in parent process ) */
            close(write_pipe[0]);

            /* Receive data from client and pass it to the server logic */
            while((status = recv(fd_client, buffer, BUFFER_SIZE, 0)) > 0) {
                if(write(write_pipe[1], buffer, status) != status)
                    ERROR("Could not pass message to server logic.\n");
            }

            /* Was receiving successful? */
            if(status < 0) ERROR("Error receiving from client.\n");

            /* Close the write pipe since we are finished receiving */
            close(write_pipe[1]);
            
            /* Read the response from the server logic and send it to the client */
            while((status = read(read_pipe[0], buffer, BUFFER_SIZE)) > 0) {
                if(send(fd_client, buffer, status, 0) < 0)
                    ERROR("Could not send response to client.\n");
            }

            /* Close the client socket */
            close(fd_client);
            
            break;
    }
}

/**
 * This function is a signal handler which waits for "dying" child processes.
 * It is used to avoid zombie processes, which fill up the process table.
 * 
 * @param [in] signum   - The signal number catched (always 17 = SIGCHLD)
 */
void sigchld_handler(int signum) {
    while(waitpid(-1, NULL, WNOHANG) > 0);
}

/**
 * This function registers a signal handler, which catches the SIGCHLD signal.
 * It is used to catch the return value of a closing child process and avoid
 * it to go zombie.
 */
void register_signal_handler() {
    struct sigaction sa;                /* Signal handling configuration */
    
    /* Define the signal handler for removing stopped child processes*/
    sa.sa_handler = sigchld_handler;

    /* Initialize the signal configuration */
    sigemptyset(&sa.sa_mask);

    /* Return back to last action after signal was received */
    sa.sa_flags = SA_RESTART;

    /* Pass the signal handler to the system */
    if(sigaction(SIGCHLD, &sa, NULL) == -1)
        ERROR("Could not create signal handler\n");
}

/**
 * This function puts the server into online mode. It creates a socket for
 * listening and forks the server logic if a client connects to the server.
 */
void start_communication(void) {
    struct sockaddr_storage clients;    /* Storage for client connections */
    socklen_t addr_size;                /* Client storage length*/
    int pid;                            /* Process ID used for forking */
    int fd_socket = 0;                  /* Socked used for listening */
    int fd_client = 0;                  /* Socket used for client communication */
    
    /* Create a server socket for listening */
    create_listener_socket(&fd_socket);

    /* Listen on the socket, allow n simultanous connection attempts */
    listen(fd_socket, BACKLOG);

    /* Register the signal handler for disposing stopped child processes */
    register_signal_handler();

    /* Start the endless server loop */
    while(1) {
        /* Accept incoming connections on listener socket ->
         * create new communication socket fd_client on connection */
        addr_size = sizeof clients;
        fd_client = accept(fd_socket, (struct sockaddr *)&clients, &addr_size);

        /* Did the accept work? */
        if(fd_client == -1) ERROR("Could create socket for client\n");

        switch(pid = fork()) {
            case -1: ERROR("Could not fork child process for client communication\n");
            case 0: /* Client communication process */

                /* Close listener socket not needed in this process */
                close(fd_socket);

                /* Start the server logic to communicate with the connected client */
                link_server_logic(fd_client);

                /* Finished communication - close socket */
                close(fd_client);

                /* Exit successful */
                exit(0);
               
                break;
            default: /* Server continueing listening for more connections */
                /* Just close client socket not needed for listener */
                close(fd_client);
        }
        
    }
}

/**
 * Main function of the simple_message_server_1186
 *
 * @param [in] argc  - Number of command line arguments
 * @param [in] argv  - Command line arguments
 * @return 0 on success
 */
int main(int argc, char** argv) {
    /* Get application name for usage print */
    cfg.applicationName = *argv;
    
    /* Parse command line arguments */
    parse_arguments(argc, argv);

    /* Check client options for completeness */
    if( cfg.help == 1 || cfg.port == NULL) {
        print_usage();
    } else {
        start_communication();
    }

    return (EXIT_SUCCESS);
}

